import _ from 'lodash';
import mongoose from 'mongoose';
import assert from 'assert';
import Router from 'koa-router';
import Channel from '../models/channel';
import User from '../models/user';
import ChannelUser from '../models/channelUser';
import Message from '../models/message';
import tokenAuth from '../middleware/tokenAuth';
import channelHandlers from '../handler/channelHandlers';

const router = new Router({
  prefix: '/api/channels',
});

router.use(tokenAuth());

router.post('/info', async (ctx) => {
  const { channel: channelId } = ctx.request.body;

  const channel = await Channel.findById(channelId)
    .populate('creator')
    .populate({
      path: 'members',
      select: '_id username nickname headImgUrl jiuquId userSocialIds',
    })
    .populate('admins')
    .populate('customer');

  assert(channel, 'channel not exist');

  const channelUser = await channel.getChannelUser(ctx.viewer);
  const unreadCount = channelUser ? await channelUser.getUnreadCount() : 0;
  const customerInfo = _.invoke(channel, 'customer.getBasicInfo');

  let customerLastActiveAt = null;
  if (channel.customer) {
    const customerChannelUser = await channel.getChannelUser(channel.customer);
    customerInfo.lastActiveSocialId = customerChannelUser.lastActiveSocialId;
    customerLastActiveAt = _.get(customerChannelUser, 'lastActiveAt');
  }

  ctx.body = {
    ok: true,
    channel: {
      unreadCount,
      id: channel.id,
      name: channel.name,
      creator: _.invoke(channel, 'creator.getBasicInfo'),
      customer: customerInfo,
      members: channel.members.map(m => m.getBasicInfo()),
      isMember: Boolean(channelUser),
      lastActiveAt: customerLastActiveAt || channel.lastActiveAt,
    },
  };
});

router.post('/read', async (ctx) => {
  const { channel: channelId } = ctx.request.body;

  await channelHandlers.read(channelId, ctx.viewer);

  ctx.body = {
    ok: true,
  };
});

router.post('/create', async (ctx) => {
  const {
    name, isPrivate, purpose, members,
  } = ctx.request.body;

  // prettier-ignore
  const newChannel = new Channel({
    name, isPrivate, creator: ctx.viewer, purpose, group: ctx.viewer.group,
  });

  try {
    await newChannel.save();
  } catch (e) {
    if (e.message.indexOf('E11000 duplicate key') === 0) e.message = 'channel_name_exist';
    throw e;
  }

  // join the channel
  await newChannel.addMember(ctx.viewer);
  await newChannel.inviteByUserIds(members);

  const channelCreatedEvent = {
    isPrivate,
    id: newChannel.id,
    name: newChannel.name,
    members: newChannel.members,
    creator: {
      id: ctx.viewer.id,
      name: ctx.viewer.username,
    },
    createdAt: newChannel.createdAt,
  };

  const topic = [ctx.viewer.getRedisUserNoticeChannelName()];

  channelHandlers.publishRedisChannelCreatedEvent(topic, channelCreatedEvent);

  ctx.body = {
    ok: true,
    channel: newChannel,
  };
});

router.post('/list', async (ctx) => {
  const userChannels = await ChannelUser.find({ user: ctx.viewer }).populate({
    path: 'channel',
    populate: { path: 'group' },
  });

  const result = userChannels.map((userChannel) => {
    const { channel, unreadCount } = userChannel;
    const mark = Boolean(userChannel.mark);

    return {
      id: channel.id,
      name: channel.name,
      isPrivate: channel.isPrivate,
      members: channel.members,
      group: {
        id: _.get(channel, 'group.id'),
        name: _.get(channel, 'group.name'),
      },
      purpose: channel.purpose,
      creator: channel.creator,
      mark,
      unreadCount,
      lastActiveAt: channel.lastActiveAt,
    };
  });

  ctx.body = {
    ok: true,
    channels: result,
  };
});

router.post('/public', async (ctx) => {
  let channels = await Channel.find({
    isPrivate: false,
    members: { $nin: [ctx.viewer] },
  }).populate('creator');

  channels = channels.map(channel => ({
    id: channel.id,
    name: channel.name,
    isPrivate: false,
    members: channel.members,
    creator: channel.creator.getBasicInfo(),
    createdAt: channel.createdAt,
  }));

  ctx.body = {
    ok: true,
    channels,
  };
});

/**
 * @input channelId channel id
 * @input skip
 */
router.post('/history', async (ctx) => {
  const { channelId, skip } = ctx.request.body;
  const total = await Message.count({ toChannel: channelId });

  let list = await Message.find({ toChannel: channelId })
    .skip(skip)
    .sort('-createdAt')
    .limit(20)
    .populate({
      path: 'fromUser',
      select: '_id username nickname headImgUrl jiuquId',
    });

  list = list.map(message => ({
    id: message.id,
    user: message.fromUser.getBasicInfo(),
    channel: message.toChannel,
    fromSocialId: message.fromSocialId,
    subType: message.subType,
    body: message.body,
    pinned: message.pinned,
    pinnedBy: message.pinnedBy,
    createdAt: message.createdAt,
    mentions: message.mentions,
    isSent: message.isSent,
    messageFromCustomer: message.fromUser.isCustomer(),
  }));

  await channelHandlers.read(ctx.params.channelId, ctx.viewer);

  ctx.body = {
    ok: true,
    channelId,
    total,
    list,
  };
});

// TODO
router.post('/filter', async (ctx) => {
  const { date, assistantId } = ctx.request.body;
  const filter = {};
  if (date) {
    const beginAt = new Date(`${date} 00:00:00`);
    const endAt = new Date(`${date} 23:59:59`);
    filter.createdAt = {
      $gt: beginAt,
      $lt: endAt,
    };
  }
  if (assistantId) {
    filter.fromUser = new mongoose.Types.ObjectId(assistantId);
  }

  const aggregate = await Message.aggregate([
    {
      $match: filter,
    },
    {
      $group: {
        _id: '$toChannel',
        createdAt: { $max: '$createdAt' },
      },
    },
    {
      $sort: {
        createdAt: -1,
      },
    },
  ]);

  const channelIds = aggregate.map(c => c._id);
  const channels = await Channel.find({ _id: { $in: channelIds } });
  const channelsByChannelId = _.keyBy(
    channels.map(channel => ({
      id: channel._id.toString(),
      name: channel.name,
    })),
    'id',
  );

  ctx.body = {
    ok: true,
    channels: aggregate
      .map(a => _.assign({}, channelsByChannelId[a._id], { lastActiveAt: a.createdAt }))
      .filter(Boolean),
  };
});

router.post('/invite', async (ctx) => {
  const { channel: channelId, users: userIds } = ctx.request.body;
  assert(channelId, 'no_channel');
  assert(userIds, 'no_invite_users');

  const channel = await Channel.findById(channelId).populate('group');

  assert(channel, 'channel_not_exist');

  await channel.inviteByUserIds(userIds);

  ctx.body = {
    ok: true,
    channel: channel.getBasicInfo(),
  };
});

/**
 * @input channel => channel id
 * @input user => user id
 */
router.post('/kick', async (ctx) => {
  const { channel: channelId, user: userId } = ctx.request.body;

  assert(channelId, 'no_channel');
  assert(userId, 'no_kickee');

  const channel = await Channel.findById(channelId).populate('group');

  assert(channel, 'channel_not_exist');

  const user = await User.findById(userId);

  assert(user, 'user_not_exist');

  channelHandlers.leave(channel, user);

  ctx.body = {
    ok: true,
    channel: channel.getBasicInfo(),
  };
});

router.post('/join', async (ctx) => {
  const { channel: channelId } = ctx.request.body;

  assert(channelId, 'no_channel_id');

  const channel = await Channel.findById(channelId).populate('group');
  assert(channel, 'channel_not_exist');

  await channelHandlers.join(channel, ctx.viewer);

  ctx.body = {
    ok: true,
    channel: channel.getBasicInfo(),
  };
});

router.post('/star', async (ctx) => {
  const { channel: channelId } = ctx.request.body;

  assert(channelId, 'no_channel_id');

  const channelUser = await ChannelUser.findOne({
    channel: channelId,
    user: ctx.viewer,
  });

  assert(channelUser, 'channel_user_not_exists');

  channelUser.mark = true;
  await channelUser.save();

  ctx.body = {
    ok: true,
    channelUser: {
      channelId: channelUser.id,
      mark: channelUser.mark,
    },
  };
});

router.post('/unstar', async (ctx) => {
  const { channel: channelId } = ctx.request.body;
  assert(channelId, 'no_channel_id');

  const channelUser = await ChannelUser.findOne({
    channel: channelId,
    user: ctx.viewer,
  });

  assert(channelUser, 'channel_user_not_exists');

  channelUser.mark = false;
  await channelUser.save();

  ctx.body = {
    ok: true,
    channelUser: {
      channelId: channelUser.id,
      mark: channelUser.mark,
    },
  };
});

router.post('/leave', async (ctx) => {
  const { channel: channelId } = ctx.request.body;
  assert(channelId, 'no_channel_id');

  const channel = await Channel.findById(channelId).populate('creator');
  assert(channel, 'channel_not_exist');

  channelHandlers.leave(channel, ctx.viewer);

  if (channel.isPrivate) {
    ctx.body = {
      ok: true,
      channel: channel.getBasicInfo(),
    };
  } else {
    ctx.body = {
      ok: true,
      channel: {
        id: channel.id,
        name: channel.name,
        members: channel.members,
        isPrivate: channel.isPrivate,
        creator: {
          id: channel.creator.id,
          name: channel.creator.username,
        },
        createdAt: channel.createdAt,
      },
    };
  }
});

export default router;
